"""
Recorder agents (base class + period-specific classes)
"""

import datetime
import math
import time
import numpy as np
import multiprocessing as mp
import agent
from system import infogetter
from misc import misc
from misc import timedata
import system.navalasystem as na
import misc.quoteperiod as qp
from misc import parts
from misc import constants

AS_CHECK_DATA = 901 # Verifies what's recorded and what's available;
                   # Draws a plan of recordings
AS_RECORD = 902 # Performs some recording;
AS_WAIT = 903 # Market is closed

class HistoryRecorder(agent.Agent, parts.Stocker):
    """
    Historical Stock recorder base class.
    
    Recorder contains a state machine. The Cycle() method executes one machine
    step. The variables necessary for the machine to run are stored in the
    data property (dictionary).
    
    """
    ## Setup variables
    # Whether to reinitiate when finished
    flagReinitiateWhenFinished = False
    # Whether to remember the stocks already recorded when reinitiates.
    # This makes sense if stockNames has changed through "setnames" message.
    # Stocks already recorded won't be revisited.
    # Update: I am unsure whether I still want to use this. It is still working,
    # but I am not using this funcionality.
    flagRememberRecordedStocks = False
    # Whether to tell the Process Monitor about new data.
    # This is typically deactivated in the bulk recorders and activated in the
    # Min1Recorder used by the Realtime Manager
    flagTellNewData = False
    # infogetter.InfoGetter instance
    infoGetter = None
    # Recording period given as an qp
    period = None
    # Number of days in a single interval calculated by SplitInterval
    intervalSize = -1
    # Minimum date to get data from
    minDatetime = constants.DailyMinDatetime
    # How many seconds to wait until restart
    sleepTime = 5
    
    
    ## Internal states
    # flagPaused property. Read-only.
    # To set internally, use _SetFlagPaused()
    @property
    def flagPaused(self):
        return bool(self.__flagPaused.value)
    def _SetFlagPaused(self, x):
        self.__flagPaused.value = x
        self.logger.info("_ v _" if x else "O v O")
    
    recordedStocks = {}

    def __init__(self, *args, **kwargs):
        super(HistoryRecorder, self).__init__(*args, **kwargs)
        self.__flagPaused = mp.Value('b', False)

    def _TreatQueueItem(self, token, data):
        """Treats "Pause", "SetNames".
        
        ("Pause", flagYes) -- pauses/unpauses recorder. A pause blocks 
            everything in Cycle_(). Pausing is not persistent.
                             
        ("SetNames", [name0, name1, ...]) -- sets stockNames,
            and reinitiates recorder. If the name list is not empty, the recorder
            is also unpaused.
        """
        if token == "Pause":
            self._SetFlagPaused(data)
        elif token == "SetNames":
            self.SetNames2(data)
            self.flagReinitiate = True
            if data:
                self._SetFlagPaused(False)
        else:
            super(HistoryRecorder, self)._TreatQueueItem(token, data)
        
    def Cycle_(self):
        if self.flagPaused:
            self.Sleep(1)
        else:
            state = self.GetState()
            if state == agent.AS_INITIAL:
                self._UpdateAllNames()
                self.logger.info("Stockset %s" % self._stocksets)
                self.logger.info("Stock names: %s" % self._allNames)
                self.data["stocks"] = self._allNames
                self.data["iStock"] = 0
                # -1 means that last recording date needs to be found
                self.data["iInterval"] = -1
                self.SetState(AS_RECORD)
                #self.flagPause = False
            elif state == AS_RECORD:
                self.Record()

    def Record(self):
        """Records one interval and increments pointers."""
        flagContinue = True
        while True: # This loop is just to find next stock and interval
            if self.data["iStock"] >= len(self.data["stocks"]):
                flagContinue = False
                break
            
            if self.data["iInterval"] == -1: # i.e., find next stock
                name = self.data["stocks"][self.data["iStock"]]
                if self.flagRememberRecordedStocks and \
                   self.recordedStocks.has_key(name):
                    self.data["iStock"] += 1
                    continue
                ts = na.vala.db.quote_GetMaxHistTimestamp(self.period, name)
                #self.logger.debug("aaaaaaaaaa Max datetime for name=%s; period=%d: %s" % (name, self.period, misc.ts2dt(ts)))
                dt1 = misc.ts2dt(ts) if ts is not None \
                      else self.minDatetime
                dt2 = datetime.datetime.today()
                self.data["intervals"] = self.SplitInterval(dt1, dt2)
                self.data["iInterval"] = 0
            
            if self.data["iInterval"] >= len(self.data["intervals"]):
                self.recordedStocks[self.data["stocks"][self.data["iStock"]]] = True
                self.data["iStock"] += 1
                self.data["iInterval"] = -1
            else:
                break   
        
        if not flagContinue:
            if self.flagReinitiateWhenFinished:
                self.SetState(agent.AS_INITIAL) # Restart everything
                self.SaveData() # Secures data before sleeping
                if self.flagPauseWhenFinished:
                    self.logger.debug("Finished and paused")
                    self._SetFlagPaused(True)
                else:
                    self.Sleep(self.sleepTime)
            else:
                # Tells to exit but sets state to AS_INITIAL, so next time the
                # agent will restart
                self.flagExit = True
                self.SetState(agent.AS_INITIAL) # Restart everything
                self.SaveData() # Secures data before sleeping
                self.logger.info("I finished my project for now")

            self.SendMessage("Finished", self.data["stocks"])

            return
        
        interval = self.data["intervals"][self.data["iInterval"]]
        name = self.data["stocks"][self.data["iStock"]]
        
        # debugging
        self.logger.info("Stock %s, interval %s - %s" % 
            (self.data["stocks"][self.data["iStock"]],
             interval[0].strftime("%Y-%m-%d %H:%M"), 
             interval[1].strftime("%Y-%m-%d %H:%M")))
        

        try:
            quotes = self.infoGetter.GetHistory(misc.Name2Symbol(name), 
                interval[0], interval[1], self.period)
            if len(quotes) > 0:
                na.vala.db.quote_Record(self.period, name, quotes)
                self.logger.debug("...recorded %d quotes!" % len(quotes))
                if self.flagTellNewData:
                    self.SendMessage("NewData", {"names": [name], "periods": [self.period]})
            else:
                self.logger.debug("...got empty quotes data (why?)")
            self.data["iInterval"] += 1
        except:
            self.logger.exception("Error getting history, BUT WILL RETRY!!!")
            self.Sleep(3)

    def IncrementTime(self, dt):
        """Converts datetime value to next day at 0h00."""
        return (dt+datetime.timedelta(days=1)).replace(hour=0, minute=0)

    def SplitInterval(self, dt1, dt2):
        """Splits interval into smaller ones of maximum intervalSize days."""
        self.logger.debug("INTERVAL TO SPLIT: %s -- %s" % (dt1, dt2))
        a = []        
        d1 = dt1
        flagBreak = False
        flagFirst = True
        while not flagBreak:
            d2 = d1+datetime.timedelta(days=(self.intervalSize-1))
            if d2 > dt2:
                d2 = dt2
                flagBreak = True
                
            # In min1, second period on will read from 0:00
            # "self.period >= qp.daily" is not very good OOP practice
            if not flagFirst or self.period >= qp.daily: 
                d1 = d1.replace(hour=0, minute=0)
            d1 = d1.replace(second=0, microsecond=0)
            d2 = d2.replace(hour=23, minute=59, second=0, microsecond=0)
            self.logger.debug("INTERVAL: %s -- %s" % (d1, d2))
            a.append((d1, d2))
            if not flagBreak:
                d1 += datetime.timedelta(days=(self.intervalSize))
                if d1 > dt2:
                    break
            flagFirst = False
        return a

class Min1Recorder(HistoryRecorder):
    period = qp.min1
    # Number of days to read at once. Just a few in this case, because there
    # are a lot of quotes per day.
    intervalSize = 15
    minDatetime = constants.IntradayMinDatetime
    
    def IncrementTime(self, dt):
        """Adds one minute."""
        return dt+datetime.timedelta(minutes=1)

class DailyRecorder(HistoryRecorder):
    period = qp.daily
    # Number of days to read at once
    intervalSize = 500

class WeeklyRecorder(HistoryRecorder):
    period = qp.weekly
    intervalSize = 500*7
    
class MonthlyRecorder(HistoryRecorder):
    period = qp.monthly
    intervalSize = 500*30

class BimonthlyRecorder(HistoryRecorder):
    period = qp.bimonthly
    intervalSize = 500*60

class QuarterlyRecorder(HistoryRecorder):
    period = qp.quarterly
    intervalSize = 500*90

class SemesterlyRecorder(HistoryRecorder):
    period = qp.semesterly
    intervalSize = 500*180

class YearlyRecorder(HistoryRecorder):
    period = qp.yearly
    intervalSize = 500*360


